package com.supreme.cacheserver.aspect;

import com.supreme.cacheserver.annotation.RedisLock;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.concurrent.TimeUnit;

@Slf4j
@Aspect
@Component
public class RedisLockAspect {

    @Resource
    private RedissonClient redissonClient;

    @Pointcut(value = "@within(com.supreme.cacheserver.annotation.RedisLock) || @annotation(com.supreme.cacheserver.annotation.RedisLock)")
    public void PointCut() {
    }

    private String getMethodParamRedisLockKey(Parameter[] parameters, Object[] args) {
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            boolean redisLockParamPresent = parameter.isAnnotationPresent(RedisLock.class);
            if (redisLockParamPresent) {
                return (String) args[i];
            }
        }

        return null;
    }

    @Around("PointCut()")
    public Object aroundProcess(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

        Method method = methodSignature.getMethod();
        boolean redisLockMethodPresent = method.isAnnotationPresent(RedisLock.class);
        log.debug("[RedisLock] redisLockMethodPresent = {}", redisLockMethodPresent);
        if (!redisLockMethodPresent) {
            return joinPoint.proceed();
        }

        RedisLock methodRedisLockAnno = method.getAnnotation(RedisLock.class);
        String keyPrefix = methodRedisLockAnno.keyPrefix();
        String keySep = methodRedisLockAnno.keySep();
        long waitTime = methodRedisLockAnno.waitTime();
        long leaseTime = methodRedisLockAnno.leaseTime();
        TimeUnit timeUnit = methodRedisLockAnno.timeUnit();
        boolean isTryLock = methodRedisLockAnno.tryLock();

        Object[] args = joinPoint.getArgs();
        Parameter[] parameters = method.getParameters();
        String lockKey = this.getMethodParamRedisLockKey(parameters, args);
        if (StringUtils.isBlank(lockKey)) {
            lockKey = methodRedisLockAnno.lockKey();
        }

        if (StringUtils.isBlank(lockKey)) {
            return joinPoint.proceed();
        }

        lockKey = StringUtils.join(new String[]{keyPrefix, lockKey}, keySep);
        log.debug("[RedisLock] lockKey = {} waitTime = {} leaseTime = {} isTryLock = {}", lockKey, waitTime, leaseTime, isTryLock);

        if (StringUtils.isBlank(lockKey)) {
            return joinPoint.proceed();
        }

        RLock redissonLock = redissonClient.getLock(lockKey);
        Object proceed = null;
        if (isTryLock) {
            boolean tryLockSuccess;
            if (waitTime > 0) {
                if (leaseTime > 0) {
                    tryLockSuccess = redissonLock.tryLock(waitTime, leaseTime, timeUnit);
                } else {
                    tryLockSuccess = redissonLock.tryLock(waitTime, timeUnit);
                }
            } else {
                tryLockSuccess = redissonLock.tryLock();
            }
            log.debug("[RedisLock] tryLockResult = {}", tryLockSuccess);

            if (tryLockSuccess) {
                proceed = joinPoint.proceed();
                redissonLock.unlock();
            }
        } else {
            if (leaseTime > 0) {
                redissonLock.lock(leaseTime, timeUnit);
            } else {
                redissonLock.lock();
            }

            proceed = joinPoint.proceed();
            redissonLock.unlock();
        }

        return proceed;
    }
}
